package bsh.dyn;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

/**
 * Wrapps a {@link Class} into an {@link InvocationTargetImpl}
 * 
 * @author takeshi
 */
public class InvocationTargetImpl implements InvocationTarget {

    private final Class<?> javaType;

    /**
     * C'tor
     * 
     * @param javaType {@link Class}
     */
    public InvocationTargetImpl(Class<?> javaType) {
        super();
        this.javaType = javaType;
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends Invocable> methodsWithName(String name) {
        return allMethodsWithName(name, javaType, new HashSet<Invocable>());
    }

    /**
     * Collects all methods with a given name and puts them into the given {@link Collection}. Recurses through the superclasses and
     * implemented interfaces from the given {@link Class}
     * 
     * @param name the names of the method
     * @param type the type that declares the methods
     * @param col the collection to insert them into
     * @return {@link Collection}
     */
    private static Collection<Invocable> allMethodsWithName(String name, Class<?> type, Collection<Invocable> col) {
        if (type != null) {
            if (Modifier.isPublic(type.getModifiers())) {
                for (Method m : type.getMethods()) {
                    if (m.getName().equals(name)) {
                        col.add(new MethodInvocable(m));
                    }
                }
            }
            for (Class<?> iface : type.getInterfaces()) {
                allMethodsWithName(name, iface, col);
            }
            allMethodsWithName(name, type.getSuperclass(), col);
        }
        return col;
    }

    /**
     * {@inheritDoc}
     */
    public Collection<? extends Invocable> getConstructors() {
        List<Invocable> list = new ArrayList<Invocable>();
        for (Constructor<?> ctor : this.javaType.getConstructors()) {
            if (Modifier.isPublic(ctor.getModifiers())) {
                list.add(new ConstructorInvocable(ctor));
            }
        }
        return list;
    }

    /**
     * Returns all static methods for the given name
     * 
     * @param methodName String
     * @return {@link Collection}
     */
    public Collection<? extends Invocable> staticMethodsWithName(String methodName) {
        List<Invocable> invocables = new ArrayList<Invocable>();
        if (Modifier.isPublic(this.javaType.getModifiers())) {
            for (Method m : this.javaType.getMethods()) {
                if (Modifier.isStatic(m.getModifiers()) && Modifier.isPublic(m.getModifiers()) && methodName.equals(m.getName())) {
                    invocables.add(new MethodInvocable(m));
                }
            }
        }
        return invocables;
    }

}
